package org.tldgen.model; import com.sun.javadoc.AnnotationDesc; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.Doc; import com.sun.javadoc.FieldDoc; import com.sun.javadoc.MemberDoc; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.Parameter; import com.sun.javadoc.ProgramElementDoc; import com.sun.javadoc.Type; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.tldgen.annotations.BodyContent; import org.tldgen.annotations.ExcludeProperties; import org.tldgen.annotations.VariableScope; import org.tldgen.util.JavadocUtils; import javax.servlet.jsp.tagext.TagExtraInfo; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import static org.tldgen.util.JavadocUtils.getAnnotation; import static org.tldgen.util.JavadocUtils.getAnnotationArrayAttribute; import static org.tldgen.util.JavadocUtils.getBooleanAttribute; import static org.tldgen.util.JavadocUtils.getClassAttribute; import static org.tldgen.util.JavadocUtils.getEnumAttribute; import static org.tldgen.util.JavadocUtils.getStringArrayAttribute; import static org.tldgen.util.JavadocUtils.getStringAttribute; /** * Information of a tag class * @author icoloma * */ public class Tag extends AbstractTldContainerElement { /** the body content type (required) */ private BodyContent bodyContent; /** true if the tag allows dynamic attributes (optional) */ private Boolean dynamicAttributes = false; /** the list of attributes */ private SortedSet<Attribute> attributes = new TreeSet<Attribute>(); /** the list of attributes sorted by name */ private Map<String, Attribute> attributesByName = new HashMap<String, Attribute>(); /** list of Variables used by this tag */ private List<Variable> variables = new ArrayList<Variable>(); /** An optional {@link TagExtraInfo} */ private String teiClass; /** primitive wrapper types used in tld tag attribute types. Void.class excluded */ private static final Map<String,String> PRIMITIVES = new HashMap<String, String>(); static { PRIMITIVES.put(boolean.class.getSimpleName(), Boolean.class.getName()); PRIMITIVES.put(byte.class.getSimpleName(), Byte.class.getName()); PRIMITIVES.put(char.class.getSimpleName(), Character.class.getName()); PRIMITIVES.put(short.class.getSimpleName(), Short.class.getName()); PRIMITIVES.put(int.class.getSimpleName(), Integer.class.getName()); PRIMITIVES.put(long.class.getSimpleName(), Long.class.getName()); PRIMITIVES.put(float.class.getSimpleName(), Float.class.getName()); PRIMITIVES.put(double.class.getSimpleName(), Double.class.getName()); } private static Logger log = LoggerFactory.getLogger(Tag.class); public static Tag createInstance(ClassDoc doc) { Tag tag = new Tag(); AnnotationDesc ann = getAnnotation(doc, ExcludeProperties.class); Set<String> excludeProperties = ann == null? new HashSet<String>() : new TreeSet<String>(Arrays.asList(getStringArrayAttribute(ann, "value"))); recollectTagData(doc, tag, excludeProperties); return tag; } @Override public void postProcessElement(ProgramElementDoc doc, AnnotationDesc annotation) { super.postProcessElement(doc, annotation); // body content String bodyContent = getEnumAttribute(annotation, "bodyContent"); this.setBodyContent(bodyContent == null? BodyContent.SCRIPTLESS : BodyContent.valueOf(bodyContent)); // dynamic attributes Boolean dynamicAttributes = getBooleanAttribute(annotation, "dynamicAttributes"); this.setDynamicAttributes(dynamicAttributes != null? dynamicAttributes : false); // tei ClassDoc teiClassName = getClassAttribute(annotation, "teiClass"); if (teiClassName != null && !teiClassName.toString().equals(TagExtraInfo.class.getName())) { this.setTeiClass(teiClassName.toString()); } // variables AnnotationDesc[] variables = getAnnotationArrayAttribute(annotation, "variables"); if (variables != null && variables.length > 0) { for (AnnotationDesc variableAnnotation : variables) { addVariable(null, variableAnnotation); } } } private static void recollectTagData(ClassDoc doc, Tag tag, Set<String> excludeProperties) { if (!Object.class.getName().equals(doc.qualifiedName())) { recollectTagData(doc.superclass(), tag, excludeProperties); } // process any @Tag annotation AnnotationDesc annotation = getAnnotation(doc, org.tldgen.annotations.Tag.class); if (annotation != null) { tag.postProcessElement(doc, annotation); } // add annotated attributes for (FieldDoc fieldDoc : doc.fields()) { Attribute attribute = addMember(fieldDoc, tag, excludeProperties); if (attribute != null) { AnnotationDesc variableAnnotation = getAnnotation(fieldDoc, org.tldgen.annotations.Variable.class); if (variableAnnotation != null) { tag.addVariable(attribute.getName(), variableAnnotation); } } } // add annotated setter methods for (MethodDoc methodDoc : doc.methods()) { addMember(methodDoc, tag, excludeProperties); } } private static Attribute addMember(MemberDoc doc, Tag tag, Set<String> excludeProperties) { Attribute attribute = parseAttribute(doc); if (attribute != null) { if (excludeProperties.contains(attribute.getName())) { log.debug("Skipped " + tag.getName() + "." + attribute.getName() + " because it was contained in @ExcludeProperties"); } else { tag.addAttribute(attribute); } } return attribute; } /** * Parse a Variable annotation. If bound to an attribute, use the nameFromAttribute * @param attributeName if bound to an attribute, the attribute name. Otherwise, null * @param annotation the Variable annotation */ private Variable addVariable(String attributeName, AnnotationDesc annotation) { Variable variable = new Variable(); String nameGiven = JavadocUtils.getStringAttribute(annotation, "nameGiven"); if (attributeName != null) { if (nameGiven != null && nameGiven.length() > 0) { throw new IllegalArgumentException("Cannot specify @Variable.nameGiven bound to attribute '" + attributeName + "'. Use @Tag.variables instead"); } variable.setNameFromAttribute(attributeName); } else { if (nameGiven == null || nameGiven.length() == 0) { throw new IllegalArgumentException("Missing @Variable.nameGiven value"); } variable.setNameGiven(nameGiven); } Boolean declare = getBooleanAttribute(annotation, "declare"); if (declare != null) { variable.setDeclare(declare); } ClassDoc variableClass = getClassAttribute(annotation, "variableClass"); if (variableClass != null) { variable.setVariableClass(variableClass); } String scopeValue = getEnumAttribute(annotation, "scope"); if (scopeValue != null) { variable.setScope(VariableScope.valueOf(scopeValue)); } String description = getStringAttribute(annotation, "description"); if (description != null && description.length() > 0) { variable.setDescription(description); } return this.addVariable(variable); } /** * Parse a field or method javadoc and return the instantiated {@link Attribute} instance * @param doc the javadoc to parse * @return the created Attribute instance, null if none */ private static Attribute parseAttribute(MemberDoc doc) { AnnotationDesc annotation = getAnnotation(doc, org.tldgen.annotations.Attribute.class); if (annotation == null) { return null; } Attribute attribute = new Attribute(); attribute.postProcessElement(doc, annotation); Boolean required = getBooleanAttribute(annotation, "required"); attribute.setRequired(required != null? required : false); Boolean rtexprvalue = getBooleanAttribute(annotation, "rtexprvalue"); attribute.setRtexprvalue((rtexprvalue != null)? rtexprvalue : true); attribute.setType(parseAttributeType(doc)); return attribute; } /** * Parse the type from the field or method. Defaults to java.lang.String * @param doc the javadoc to parse * @return The qualified type of the attribute. */ private static String parseAttributeType(MemberDoc doc) { Type type = null; if(doc instanceof FieldDoc) { FieldDoc fieldDoc = (FieldDoc) doc; type = fieldDoc.type(); } if(doc instanceof MethodDoc) { MethodDoc methodDoc = (MethodDoc) doc; Parameter[] parameter = methodDoc.parameters(); if(parameter.length == 1) type = parameter[0].type(); } if(type == null) { return null; } else if(type.isPrimitive()) { return PRIMITIVES.get(type.qualifiedTypeName()); } else { return type.qualifiedTypeName(); } } @Override protected String calculateDefaultElementName(Doc doc) { // calculate default tag name String name = StringUtils.substringBeforeLast(doc.name(), "Tag"); name = Character.toLowerCase(name.charAt(0)) + name.substring(1); return name; } public Attribute addAttribute(Attribute attribute) { Attribute oldValue = removeAttribute(attribute.getName()); log.debug("Adding " + getName() + "." + attribute.getName()); attributes.add(attribute); attributesByName.put(attribute.getName(), attribute); return oldValue; } public Variable addVariable(Variable variable) { this.variables.add(variable); return variable; } /** * Remove an attribute if it exists, do nothing if not * @param name the name of the attribute * @return the removed attribute, if any. Null if not found */ public Attribute removeAttribute(String name) { Attribute attribute = getAttribute(name); if (attribute != null) { attributes.remove(attribute); attributesByName.remove(attribute.getName()); } return attribute; } /** * Return an attribute. * @param name the attribute name * @return the attribute, if it exists. Null if not found */ public Attribute getAttribute(String name) { return attributesByName.get(name); } public BodyContent getBodyContent() { return bodyContent; } public void setBodyContent(BodyContent bd) { this.bodyContent = bd; } public Boolean isDynamicAttributes() { return dynamicAttributes; } public void setDynamicAttributes(Boolean dynamicAttributes) { this.dynamicAttributes = dynamicAttributes; } public Collection<Attribute> getAttributes() { return attributes; } public String getTeiClass() { return teiClass; } public void setTeiClass(String teiClass) { this.teiClass = teiClass; } public List<Variable> getVariables() { return variables; } }